package org.xtremeobjects.domain.aspects.javabean;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Field;
import org.aspectj.lang.Signature;
import org.xtremeobjects.anotations.Hidden;
import org.xtremeobjects.anotations.PropertyChanger;

/**
 * This aspect adds JavaBean Property Change support for POJOS
 * with a @JavaBean annotation.
 * All fields of a class anotated with @JavaBean fires property change
 * events after they have been setted.
 */
public aspect PropertySupportAspect {

	transient PropertyChangeSupport PropertySupport.support = new PropertyChangeSupport(
			this);


	@Hidden
	public void PropertySupport.addPropertyChangeListener(
			PropertyChangeListener listener) {

		support.addPropertyChangeListener(listener);

	}

	@Hidden
	public void PropertySupport.addPropertyChangeListener(String propertyName,
			PropertyChangeListener listener) {

		support.addPropertyChangeListener(propertyName, listener);

	}

	@Hidden
	public void PropertySupport.removePropertyChangeListener(
			String propertyName, PropertyChangeListener listener) {
		support.removePropertyChangeListener(propertyName, listener);
	}

	@Hidden
	public void PropertySupport.removePropertyChangeListener(
			PropertyChangeListener listener) {
		support.removePropertyChangeListener(listener);
	}

	@Hidden
	public boolean PropertySupport.hasListeners(String propertyName) {
		return support.hasListeners(propertyName);
	}

	@Hidden
	public void PropertySupport.firePropertyChange(Object b, String property,
			Object oldval, Object newval) {
/*
 		System.out.println("The property is [" + property + "]");
		System.out.println("The old value is [" + oldval + "]");
		System.out.println("The new value is [" + newval + "]");
		/**/
		support.firePropertyChange(property,
				(oldval == null) ? oldval : oldval, newval);

	}

	/**
	 * This point cut mtches all the sets on all the fields of
	 * a class that has been marked by @JavaBean.
	 */
	 pointcut changeField( PropertySupport b ) : set(* *) &&
	 target(b) && within(PropertySupport+);

	 /**
	  * This advice fires property change events for all the
	  * fields changes at @JavaBean anotated classes
	  */
	 before( PropertySupport b ) : changeField( b )
	 {
		String propertyName = thisJoinPointStaticPart.getSignature().getName();
		Field field = getFieldFromFieldSignature(thisJoinPointStaticPart.getSignature());

		// Get old field value
		Object oldValue = null;
		try {
			field.setAccessible(true);
			oldValue = field.get((Object)b);
		} catch (Throwable ex) {
			ex.printStackTrace();
		}
		Object newValue = thisJoinPoint.getArgs()[0];
		b.firePropertyChange(b, propertyName, oldValue, newValue);
	 }

	 /**
	  * This pointcut matches the setters that have @PropertyChanger
	  * anotation on classes already marked by @JavaBean.
	  */
	pointcut callSetter(PropertySupport b)
   : call( @PropertyChanger * set*(..) ) && target( b ) && within(PropertySupport+);

	/**
	 * This advice fires a property change on the return of a setter
	 * matched.
	 */
	void around(PropertySupport b) : callSetter( b )  {

		Field field = getFieldFromMethod(thisJoinPointStaticPart.getSignature());
		String propertyName = field.getName();
		System.out.println("The property is [" + propertyName + "]");

		// Get parameter for the setter (assumes it has just one parameter)
		Object newValue = thisJoinPoint.getArgs()[0];

		// Get old field value
		Object oldValue = null;
		try {
			field.setAccessible(true);
			oldValue = field.get(b);
		} catch (Exception ex) {
			ex.printStackTrace();
		}

		proceed(b);
		b.firePropertyChange(b, propertyName, oldValue, newValue);
	}

	/**
	 * Gets a Field from a signature of a field.
	 */
	private Field getFieldFromFieldSignature( Signature signature )
	{
		return getField( signature.getDeclaringType(), signature.getName());
	}

	/**
	 * Gets a Field from a signature of a Setter/Getter method signature
	 */
	private Field getFieldFromMethod(Signature signature) {
		System.out.println("Getting the field name of [" + signature.getName()
				+ "]");

		String methodName = signature.getName();
		String propertyName = methodName.substring(3, methodName.length())
					.toLowerCase();
		return getField(signature.getDeclaringType(), propertyName);

	}

	/**
	 * Returns a Field from the signature and the field name
	 */
	private Field getField(Class clazz, String fieldName) {
		Field field = null;
		try {
			field = clazz.getDeclaredField(fieldName);
		} catch (NoSuchFieldException nsfe) {
			field = getField(clazz.getSuperclass(), fieldName);
		}
		return field;
	}

}
